package org.aceor.funny.common.http.retrofit.parser;


import com.google.common.collect.Maps;
import org.aceor.funny.common.http.executor.ResultCallback;
import org.aceor.funny.common.http.retrofit.SimpleRetrofit;
import org.aceor.funny.common.http.retrofit.annotation.*;
import org.aceor.funny.common.http.utils.TypeUtils;
import org.aceor.funny.common.http.utils.Utils;
import org.aceor.funny.common.http.utils.HttpMethod;
import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.ParseException;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Map;

/**
 * Created by lxue on 15/11/20.
 */
public final class HttpRequestFactory {
    public final static String ANNOTATION_BODY_NAME = "__BODY__";
    ResultCallback callback;
    Type clazz;//用于异步,记录callback中的实际参数类型(List时取List)
    Type returnClazz;//用于同步,记录返回值类型
    boolean async;
    String url;
    Header[] headers;
    HttpMethod httpMethod;
    String relativeUrl;
    boolean isMultipart;
    boolean hasBody;
    boolean isFormEncoded;
    Map<String,Object> data;
    Method method;
    SimpleRetrofit retrofit;
    Map<String, Object> queryMap;
    Map<String, Object> fieldMap;
    Map<String, Object> partMap;
    Object[] args;
    public HttpRequestFactory(Method method, Object[] args, SimpleRetrofit retrofit) {
        this.method = method;
        this.retrofit = retrofit;
        this.args = args;
    }

    public static HttpRequest createRequest(Method method, Object[] args, SimpleRetrofit retrofit){
        HttpRequestFactory parser = new HttpRequestFactory(method, args, retrofit);
        parser.parseMethodAnnotations();
        parser.parserMethodParameters();
        parser.parserMethodReturnType();
        parser.dataCompose();
        return new HttpRequest(parser.url, parser.httpMethod, parser.isMultipart, parser.hasBody, parser.isFormEncoded, parser.data, parser.headers, parser.callback, parser.clazz, parser.returnClazz, parser.async);
    }

    /**
     * 解析方法返回值类型,不为Void时默认采用异步,否则采用同步
     */
    private void parserMethodReturnType() {
//        returnClazz = TypeUtils.getRawType(method.getGenericReturnType());
        returnClazz = method.getGenericReturnType();
        async = void.class.isAssignableFrom(TypeUtils.getRawType(returnClazz));
    }

    private void dataCompose() {
        this.url = TypeUtils.urlCompose(retrofit.getBaseUrl() + relativeUrl, queryMap);

        if(null != fieldMap && fieldMap.size() != 0){
            if(data == null){
                data = Maps.newHashMap();
            }
            data.putAll(fieldMap);
        }
        if(null != partMap && partMap.size() != 0){
            if(data == null){
                data = Maps.newHashMap();
            }
            data.putAll(partMap);
        }
    }

    private void parserMethodParameters() {
        Type[] methodParameterTypes = this.method.getGenericParameterTypes();
        Annotation[][] annotationss = this.method.getParameterAnnotations();
        int len = annotationss.length;
        for (int i=0; i<len; i++){
            Type methodParameterType = methodParameterTypes[i];
            Annotation[] annotations = annotationss[i];
            int length = annotations.length;
            for(int j=0; j<length; j++){
                Annotation annotation = annotations[j];
                if(annotation instanceof Body){
                    if(!hasBody){
                        hasBody = true;
                    }else {
                        throw new RuntimeException("Duplicate @Body.");
                    }
                    if(isFormEncoded || isMultipart){
                        throw new RuntimeException("@Body doen not match @FormUrlEncoded or @Multipart.");
                    }
                    if(null == args[i]){
                        continue;
                    }
                    if(null == data){
                        data = Maps.newHashMap();
                    }
                    System.out.println("@Body data="+args[i]);
                    data.put(ANNOTATION_BODY_NAME,args[i]);
                }else if (annotation instanceof Query){
                    Query query = (Query) annotation;
                    String value = query.value();
                    if(query.encoded()){
                        value = Utils.urlParamEncode(value);
                    }
                    if(null == args[i]){
                        continue;
                    }
                    if(null == queryMap){
                        queryMap = Maps.newHashMap();
                    }
                    queryMap.put(value, args[i]);
                }else if (annotation instanceof Field){
                    if(!isFormEncoded){
                        throw new RuntimeException("@FormUrlEncoded must be chosen when using @Filed.");
                    }
                    Field field = (Field) annotation;
                    String value = field.value();
                    if(null == args[i]){
                        continue;
                    }
                    if(null == fieldMap){
                        fieldMap = Maps.newHashMap();
                    }
                    if(field.encoded()) {
                        fieldMap.put(value, Utils.urlParamEncode((String) args[i]));
                    }else{
                        fieldMap.put(value, args[i]);
                    }
                }else if (annotation instanceof Part){
                    if(!isMultipart){
                        throw new RuntimeException("@Multipart must be chosen when using @Part.");
                    }
                    if(!byte[].class.isAssignableFrom(TypeUtils.getRawType(methodParameterType))){
                        throw new RuntimeException("@Part can only be supported by byte[]");
                    }
                    if(null == args[i]){
                        continue;
                    }
                    if(null == partMap){
                        partMap = Maps.newHashMap();
                    }
                    Part part = (Part)annotation;
                    //采用默认的二进制
                    partMap.put(part.value(),args[i]);
                }else if(annotation instanceof Callback){
                    if(!ResultCallback.class.isAssignableFrom(TypeUtils.getRawType(methodParameterType))){
                        throw new RuntimeException("@Callback can only be supported by Interface ResultCallback");
                    }
                    if(this.callback != null){
                        throw new RuntimeException("@Callback can only be used once in a method.");
                    }
                    this.callback = (ResultCallback)args[i];
                    this.clazz = TypeUtils.getResultCallbackParameterizedType(callback);
                }else if (annotation instanceof Headers){
                    Headers headers0 = (Headers) annotation;
                    String[] headersValue = headers0.value();
                    headers = parseHeaders(headersValue);
                } else{
                    throw new RuntimeException("unknown annotation.");
                }
            }
        }
    }

    private Header[] parseHeaders(String[] headersValue) {
        int len = headersValue.length;
        if(len == 0){
            throw new IllegalArgumentException("@Headers cannot have empty content");
        }
        headers = new Header[len];
        for(int k=0; k<len; k++){
            final String[] pair = headersValue[k].split(":");
            if(pair.length != 2){
                throw new IllegalArgumentException("each content in @Headers should format with key:value");
            }
            headers[k] = new Header() {
                public String getName() {
                    return pair[0];
                }
                public String getValue() {
                    return pair[1];
                }
                public HeaderElement[] getElements() throws ParseException {
                    return new HeaderElement[0];
                }
            };
        }

        return headers;
    }

    private void parseMethodAnnotations() {
        Annotation[] annotations = this.method.getAnnotations();
        int len = annotations.length;

        for (int i=0; i<len; i++){
            Annotation annotation = annotations[i];
            if(annotation instanceof POST){
                if(null == httpMethod){
                    httpMethod = HttpMethod.POST;
                }else{
                    throw new RuntimeException("Duplicate Annotaion of HttpMethod.");
                }
                POST post = (POST)annotation;
                this.relativeUrl = post.value();
            }else if(annotation instanceof GET){
                if(null == httpMethod){
                    httpMethod = HttpMethod.GET;
                }else{
                    throw new RuntimeException("Duplicate Annotaion of HttpMethod.");
                }
                GET get = (GET)annotation;
                this.relativeUrl = get.value();
            }else if(annotation instanceof MultiPart){
                isMultipart = true;
            }else if(annotation instanceof FormUrlEncoded){
                isFormEncoded = true;
            }else{
                throw new RuntimeException("Incompatible Annotaion.");
            }
        }
        if(httpMethod == null){
            throw new RuntimeException("请求类型注解不能为空.");
        }
    }
}
